#include <cnix/string.h>
#include <cnix/errno.h>
#include <cnix/fs.h>
#include <cnix/kernel.h>

int do_mkdir(const char * pathname, mode_t mode)
{
	int error = 0;
	char part[NAME_MAX + 1];
	struct inode * fino, * inoptr;
	mode_t mode1;

	checkname(pathname);

	fino = namei(pathname, &error, 1);
	if(!fino)
		return error;

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		return -ENOTDIR;
	}

	if(admit(fino, I_WB) == 0){
		iput(fino);
		return -EACCES;
	}

	if(!get_tail(pathname, part)){
		iput(fino);
		return -ENAMETOOLONG;
	}

	fino->i_count++;
	inoptr = ifind(fino, part);
	if(inoptr){
		iput(fino);
		iput(inoptr);
		return -EEXIST;
	}

	// lock again, because has been iput in ifind
	ilock(fino);

	mode1 = ((mode & I_UGRWX) & (~(current->umask))) | I_DIR;

	inoptr = iinsert(fino, part, mode1, &error);
	if(!inoptr){
		iput(fino);
		return error;
	}	

	inoptr->i_ops->additem(inoptr, ".", &error, inoptr->i_num);
	inoptr->i_ops->additem(inoptr, "..", &error, fino->i_num);

	inoptr->i_ops->change_nlinks(inoptr, 1); // ialloc will set nlinks to 1
	inoptr->i_dirty = 1;
	iput(inoptr);

	fino->i_ops->change_nlinks(fino, 1);
	fino->i_update |= MTIME;
	fino->i_dirty = 1;
	iput(fino);

	return 0;
}

int do_rmdir(const char * pathname)
{
	int error = 0;
	char part[NAME_MAX + 1];
	struct inode * fino, * inoptr;

	checkname(pathname);

	if(!get_tail(pathname, part))
		return -ENAMETOOLONG;

	if(!strcmp(part, ".") || !strcmp(part, ".."))
		return -EINVAL;

	fino = namei(pathname, &error, 1);
	if(!fino)
		return error;

	if(!S_ISDIR(fino->i_ops->mode(fino))){
		iput(fino);
		return -ENOTDIR;
	}

	if(!admit(fino, I_WB)){
		iput(fino);
		return -EACCES;
	}

	if(strlen(part) > NAME_MAX){
		iput(fino);
		return -ENAMETOOLONG;
	}

	fino->i_count++;
	inoptr = ifind(fino, part);
	if(!inoptr){
		iput(fino);
		return -ENOENT;
	}

	if((inoptr == current->pwd) || (inoptr == current->root)){
		iput(inoptr);
		iput(fino);
		return -EBUSY;
	}

	if(!S_ISDIR(inoptr->i_ops->mode(inoptr))){
		iput(inoptr);
		iput(fino);
		return -ENOTDIR;
	}

	if(!inoptr->i_ops->noitem(inoptr)){
		iput(inoptr);
		iput(fino);
		return -ENOTEMPTY;
	}

	if(inoptr->i_ops->change_nlinks(inoptr, 0) != 2)
		DIE("BUG: cannot happen\n");

	inoptr->i_ops->change_nlinks(inoptr, -2);
	inoptr->i_dirty = 1;
	iput(inoptr);

	ilock(fino); // lock again
	if(!fino->i_ops->delitem(fino, part, &error)){
		iput(fino);
		return error;
	}

	fino->i_ops->change_nlinks(fino, -1);
	fino->i_update |= MTIME;
	fino->i_dirty = 1;
	iput(fino);

	return 0;
}

int do_chdir(const char * pathname)
{
	int error = 0;
	struct inode * inoptr, * pwd;

	checkname(pathname);

	inoptr = namei(pathname, &error, 0);
	if(!inoptr)
		return error;

	if(!S_ISDIR(inoptr->i_ops->mode(inoptr))){
		iput(inoptr);
		return -ENOTDIR;
	}

	// xxx
	if(!admit(inoptr, I_RB)){
		iput(inoptr);
		return -EPERM;
	}

	pwd = current->pwd;
	if(pwd == inoptr){
		iput(inoptr);
		return 0;
	}
	
	iput(pwd);

	current->pwd = inoptr;

	inoptr->i_count++;
	iput(inoptr);

	return 0;
}

int do_chroot(const char * pathname)
{
	int error = 0;
	struct inode * inoptr, * root;

	checkname(pathname);

	inoptr = namei(pathname, &error, 0);
	if(!inoptr)
		return error;

	if(!admit(inoptr, I_RB)){
		iput(inoptr);
		return -EPERM;
	}

	root = current->root;
	if(root == inoptr){
		iput(inoptr);
		return 0;
	}
	
	iput(root);

	current->root = inoptr;

	inoptr->i_count++;
	iput(inoptr);

	return 0;
}

int do_getcwd(char * buff, size_t size)
{
	int i;
	ino_t inum;
	int error = 0, len, idx;
	char name[NAME_MAX + 1];
	struct inode * tmp, * fino;

	if(!buff)
		return -EFAULT;

	tmp = current->pwd;
	tmp->i_count++; // xxx
	ilock(tmp);

	buff[size - 1] = '\0';
	idx = size - 1;

	do{
		fino = ifind(tmp, "..");
		if(!fino)
			return -EIO;

		inum = tmp->i_num;
		if((tmp->i_flags & MOUNTED_I) && !REAL_ROOT(tmp)){
			struct super_block * sb;

			sb = sread(tmp->i_dev);
			inum = sb->s_mounti->i_num;
		}

		if(inum == current->root->i_num)
			break;

		// get_name_of_son only be called here
		// the second parameter must be the real son
		error = fino->i_ops->get_name_of_son(
			fino, inum, name, sizeof(name)
			);
		if(error < 0)
			break;

		len = strlen(name);
		if((idx - (len + 1)) < 0){
			error = -ERANGE;
			break;
		}

		idx -= len; 
		if(memcpy_to_user(&buff[idx], name, strlen(name)) < 0){
			error = -EFAULT;
			break;
		}

		idx--;
		buff[idx] = '/';

		tmp = fino;
	}while(1);

	iput(fino);

	if(error < 0)
		return error;

	if(idx >= (size - 1)){
		if(put_user1(&buff[0], '/') < 0)
			return -EFAULT;

		if(put_user1(&buff[1], '\0') < 0)
			return -EFAULT;
	}else if(idx > 0){
		for(i = 0; buff[idx]; i++, idx++){
			if(put_user1(&buff[i], buff[idx]) < 0)
				return -EFAULT;
		}

		if(put_user1(&buff[i], '\0') < 0)
			return -EFAULT;
	}

	return 0;
}
